import qs from 'qs';
import { tryit } from 'radash';

type RequestOptions = {
    url: string;
    method?: 'GET' | 'POST' | 'ROW'
    timeout?: number;
    headers?: Record<string, string>;
    params?: Record<string, any>;
    data?: Record<string, any>;
};

type FetchOptions = {

}

const pw = (promise: Promise<any>) => {
    return promise.then(data => [null, data]).catch(err => [err]);
}

interface RequestCommonConfig {
    baseURL: string;
    timeout: number;
    headers: Record<string, string>;
    fields: Record<string, any>;
    errorMessage: Record<string, string>;
    onResponse?: (requestType: 'json', data: any) => void;
}


// 配置项
const commonConfig: RequestCommonConfig = {
    baseURL: '/',
    timeout: 0,
    headers: {},
    fields: {},
    errorMessage: {
        'Network Error': '网络异常，请重试',
        'Request failed with status code 404': '服务器异常，请稍后再试'
    }
};

export function getRequestConfig() {
    return Object.freeze({
        ...commonConfig
    });
}

// ============== 设置参数 ======================

export function setBaseUrl(url: string) {
    if (url.indexOf('/') != 0 && url.indexOf('http') != 0) {
        url = '/' + url;
    }

    commonConfig.baseURL = url;
}

export function setResponseTimeout(time: number = 0) {
    commonConfig.timeout = time;
}

export function setHeaders(options = {}) {
    commonConfig.headers = {};

    Object.assign(commonConfig.headers, options);
}

export function appendHeader(key: string, value: string) {
    commonConfig.headers[key] = value;
}

export function setCommonFields(options = {}) {
    commonConfig.fields = {};

    Object.assign(commonConfig.fields, options);
}

export function appendField(key: string, value: string | number) {
    commonConfig.fields[key] = value;
}

export function setErrorMessage(errorMessage: string, showMessage: string) {
    commonConfig.errorMessage[errorMessage] = showMessage;
}

export function setResponseCallback(callback: RequestCommonConfig["onResponse"]) {
    commonConfig.onResponse = callback;
}

// ================== 请求 =====================

function ajaxErrorMessage(message: string) {
    return commonConfig.errorMessage[message] || message || '请求失败';
}

function assemblyOptions(options: RequestOptions): RequestInit {
    const fetchOptions: RequestInit = {
        method: options.method || 'GET',
        body: options.data as unknown as BodyInit,
        headers: options.headers || {},
    };

    // 整合数据
    options.data = Object.assign({}, commonConfig.fields, options.data);

    // 整合提交方式和数据格式
    if (options.method == 'ROW') {
        fetchOptions.method = 'POST';
        fetchOptions.body = JSON.stringify(options.data);
        // @ts-ignore
        fetchOptions.headers['Content-Type'] = 'text/plain;charset=UTF-8';
    } else if (options.method == 'POST') {
        delete options.headers?.['Content-Type'];
        const formData = new FormData();

        for (const key in options.data) {
            if (key in options.data) {
                if (options.data[key] != null) {
                    formData.append(key, options.data[key]);
                }
            }
        }

        fetchOptions.body = formData;
    } else if (options.headers && !('Content-Type' in options.headers)) {
        fetchOptions.body = qs.stringify(options.data);
        // @ts-ignore
        fetchOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded';
    }

    return fetchOptions;
}

const getAbortController = (timeout: number): [AbortSignal, () => void] => {
    const controller = new AbortController();
    const out = timeout ?? commonConfig.timeout ?? 0;
    let st: any;

    if (out > 0) {
        st = setTimeout(() => controller.abort(), out);
    }

    return [controller.signal, () => clearTimeout(st)]
}

const getRequestUrl = (url: string, ...args: RequestOptions["params"][]): string => {
    // 整合地址
    if (url.indexOf('/') != 0 && url.indexOf('http') != 0) {
        url = commonConfig.baseURL + url;
    }

    // url参数拼接
    const prefix = url.indexOf('http') != 0 ? 'http://xxx.xxx' : '';
    const urlInstance = new URL(prefix + url);

    args.map(params => {
        for (const paramKey in params) {
            if (!(paramKey in params)) {
                continue;
            }

            if (params[paramKey] == null) {
                continue;
            }

            urlInstance.searchParams.append(paramKey, String(params[paramKey]));
        }
    });

    url = urlInstance.toString().slice(prefix.length);

    return url;
}

export async function request(options: RequestOptions) {
    const requestUrl = getRequestUrl(options.url, options.method == 'GET' ? commonConfig.fields : {}, options.params);
    const fetchOptions = assemblyOptions(options);
    const [single, resolveSingle] = getAbortController(options.timeout || 0);
    fetchOptions.signal = single;
    const [responseError, response] = await tryit(fetch)(requestUrl, fetchOptions);
    resolveSingle();

    if (responseError) {
        if (import.meta.env.MODE === 'development') {
            console.error('[API]', requestUrl, responseError.message);
        }

        return Promise.reject(responseError);
    }

    return response;
}

class ResponseError extends Error {
    response: any;
}

export async function requestJson(options: RequestOptions) {
    const [responseError, response] = await tryit(request)(options);

    if (responseError) {
        return Promise.reject(responseError);
    }

    const [jsonError, result] = await pw(response.json());

    if (jsonError) {
        return Promise.reject(jsonError);
    }

    commonConfig.onResponse?.('json', result);

    if (result.code !== 200 || !result.success) {
        let showMsg = '';

        if (result.code === -1) {
            showMsg = result.message + 'deleteFail';
        } else {
            showMsg = result.message;
        }

        const error = new ResponseError(ajaxErrorMessage(showMsg));

        error.response = result;

        return Promise.reject(error);
    }

    return result;
}
